package com.idea.relax.log.aspect;

import com.idea.relax.log.api.custom.ICustomFieldsProvider;
import com.idea.relax.log.api.custom.ValueProvider;
import com.idea.relax.log.constant.LogConstant;
import com.idea.relax.log.constant.PropsConstant;
import com.idea.relax.log.logger.LogLevel;
import com.idea.relax.log.props.RelaxLogProperties;
import com.idea.relax.log.props.RequestLogMode;
import com.idea.relax.log.publisher.ErrorLogPublisher;
import com.idea.relax.log.support.AppInfoProvider;
import com.idea.relax.log.support.LoggerUtil;
import com.idea.relax.log.support.MethodExecuteResult;
import com.idea.relax.log.support.ResponseStatus;
import com.idea.relax.log.support.utils.*;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.MDC;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.core.MethodParameter;
import org.springframework.core.io.InputStreamSource;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author: 沉香
 * @date: 2022/12/17
 * @description: 请求日志切面的支撑类
 */
@Slf4j
public class RequestLogSupport {


	public void handleBeforeMethod(MethodInvocation invocation, RelaxLogProperties.RequestLog properties,
								   LoggerUtil logger, HttpServletRequest request) {
		Set<RequestLogMode> modes = properties.getRequestLogMode();
		//获取类名
		String methodClass = Objects.requireNonNull(invocation.getThis()).getClass().getSimpleName();
		//获取方法名
		String methodName = invocation.getMethod().getName();
		//构建请求的日志信息
		logger.addLogText(StringPool.CRLF);
		logger.addLogText(LogConstant.REQUEST_START_LOG);
		logger.addLogText("==> {}: {} \n");
		String requestMethod = request.getMethod();
		String requestUrl = Objects.requireNonNull(request).getRequestURI();
		logger.addArgs(requestMethod, requestUrl);

		if (RequestLogMode.TRACE_ID.containsOf(modes)) {
			addTraceId(logger);
		}
		//除了全量模式，其它模式不会打印请求头
		if (RequestLogMode.HEADERS.containsOf(modes)) {
			addRequestHeader(request, logger);
		}
		Map<String, String> paramsWrapper = addRequestParams(invocation, logger, modes);
		logger.addLogText(LogConstant.REQUEST_END_LOG);
		//MDC中的属性要在log日志打印之前放入
		if (properties.isAddRichFields()) {
			MDCUtil.puts(LogConstant.REQ_MDC_FIELDS, methodClass, methodName,
				requestMethod, requestUrl, paramsWrapper.get("requestParams"), paramsWrapper.get("requestBody"),
				request.getHeader(PropsConstant.USER_AGENT_HEADER), WebUtil.getIP(request), new Date());
			AppInfoProvider.appendServerInfoToMdc();
		}
		logger.addLogText("\n");
		logger.addLogText(LogConstant.RESPONSE_START_LOG);
	}


	public void handleExMethod(MethodExecuteResult methodExecuteResult,
							   Throwable e,
							   RelaxLogProperties properties,
							   HttpServletRequest request) {
		if (properties.getRequestLog().isAddRichFields()) {
			//获取异常信息
			StackTraceElement element = ExceptionUtil.getStackTraceByPrefix(e, properties.getErrorLog().getAppPackagePrefix());
			MDCUtil.puts(LogConstant.ERR_MDC_FIELDS,
				e.getClass().getName(), e.getMessage(), element.getFileName(), element.getLineNumber());
		}
		if (properties.getErrorLog().isEnabled()) {
			ErrorLogPublisher.build(methodExecuteResult, request).publish();
		}
	}

	public void handleAfterMethod(LoggerUtil logger, ObjectProvider<ICustomFieldsProvider> objectProvider,
								  String requestMethod, String requestUrl,
								  RelaxLogProperties.RequestLog properties,
								  MethodExecuteResult methodExecuteResult) {

		logger.addLogText("<== {}: {} ({} ms) \n");
		logger.addArgs(requestMethod, requestUrl, methodExecuteResult.getTime());
		logger.addLogText("== Response status ==  {} \n");
		logger.addArgs(methodExecuteResult.getResponseStatus().getState());
		logger.addLogText("== Response result ==  {} \n");
		String responseData = subResponseData(methodExecuteResult.getResult(), properties);
		logger.addArgs(responseData);
		if (properties.isAddRichFields()) {
			MDCUtil.puts(LogConstant.RESP_MDC_FIELDS,
				methodExecuteResult.getResponseStatus().getState(), responseData, methodExecuteResult.getTime());
		}
		logger.addLogText(LogConstant.RESPONSE_END_LOG);
		resolveCustomFieldsProviders(objectProvider);
		if (ResponseStatus.SUCCESS.equals(methodExecuteResult.getResponseStatus())) {
			logger.log(LogLevel.INFO);
		} else {
			logger.setThrowable(methodExecuteResult.getEx());
			logger.log(LogLevel.ERROR);
		}

	}

	private void resolveCustomFieldsProviders(ObjectProvider<ICustomFieldsProvider> objectProvider) {
		Iterator<ICustomFieldsProvider> iterator = objectProvider.stream().iterator();
		while (iterator.hasNext()) {
			ICustomFieldsProvider fieldsProvider = iterator.next();
			Map<String, ValueProvider> providersMap = fieldsProvider.getProviders();
			List<String> fields = new ArrayList<>();
			List<String> values = new ArrayList<>();
			if (null == providersMap) {
				continue;
			}
			providersMap.forEach((field, provider) -> {
				if (StringUtil.isBlank(field)) {
					return;
				}
				fields.add(field);
				String value = provider.get();
				values.add(StringUtil.isBlank(value) ? StringPool.NULL : value);
			});
			MDCUtil.putsFromStrList(fields, values);
		}
	}

	private void addTraceId(LoggerUtil logger) {
		logger.addLog("==> traceId: {} \n", Func.get(MDC.get(PropsConstant.TRACE_ID), "N/A"));
	}

	/**
	 * 添加请求头相关的日志信息
	 *
	 * @param request
	 * @param logger
	 */
	private void addRequestHeader(HttpServletRequest request, LoggerUtil logger) {
		Enumeration<String> headerNames = request.getHeaderNames();
		if (headerNames != null) {
			while (headerNames.hasMoreElements()) {
				String name = headerNames.nextElement();
				String header = request.getHeader(name);
				logger.addLogText("== Request header ==  {}: {} \n");
				logger.addArgs(name, header);
			}
		}
	}

	/**
	 * 解析请求参数相关的日志信息
	 *
	 * @param invocation
	 * @param logger
	 */
	private Map<String, String> addRequestParams(MethodInvocation invocation, LoggerUtil logger, Set<RequestLogMode> modes) {
		Map<String, String> paramsWrapper = new HashMap<>(16);
		Object[] args = invocation.getArguments();
		Method method = invocation.getMethod();
		Map<String, Object> paramsMap = new HashMap<>(16);
		for (int i = 0; i < args.length; i++) {
			Object paramValue = args[i];
			MethodParameter methodParameter = ClassUtil.getMethodParameter(method, i);
			PathVariable pathVariable = methodParameter.getParameterAnnotation(PathVariable.class);
			if (pathVariable != null) {
				continue;
			}
			String parameterName = methodParameter.getParameterName();
			RequestBody requestBody = methodParameter.getParameterAnnotation(RequestBody.class);
			RequestParam requestParam = methodParameter.getParameterAnnotation(RequestParam.class);
			if (requestParam != null && StringUtil.isNotBlank(requestParam.value())) {
				parameterName = requestParam.value();
			}
			if (requestBody != null) {
				String body = JsonUtil.toJson(paramValue);
				paramsWrapper.put("requestBody", body);
				if (RequestLogMode.REQ_BODY.containsOf(modes)) {
					logger.addLogText("== Request body == {} \n");
					logger.addArgs(body);
				}
			} else {
				if (paramValue instanceof HttpServletRequest) {
					paramsMap.putAll(((HttpServletRequest) paramValue).getParameterMap());
				} else if (paramValue instanceof WebRequest) {
					paramsMap.putAll(((WebRequest) paramValue).getParameterMap());
				} else if (paramValue instanceof MultipartFile) {
					String filename = ((MultipartFile) paramValue).getOriginalFilename();
					paramsMap.put(parameterName, filename);
				} else if (paramValue instanceof MultipartFile[]) {
					MultipartFile[] files = (MultipartFile[]) paramValue;
					if (files.length > 0) {
						List<String> filenames = Stream.of(files)
							.map(MultipartFile::getOriginalFilename)
							.collect(Collectors.toList());
						paramsMap.put(parameterName, StringUtil.join(filenames));
					}
				} else {
					if (paramValue == null) {
						paramsMap.put(parameterName, null);
					} else if (paramValue instanceof InputStream) {
						paramsMap.put(parameterName, "InputStream对象");
					} else if (paramValue instanceof InputStreamSource) {
						paramsMap.put(parameterName, "InputStreamSource对象");
					} else if (JsonUtil.isCanSerialize(paramValue)) {
						paramsMap.put(parameterName, paramValue);
					} else {
						paramsMap.put(parameterName, "此参数不支持序列化为json");
					}
				}
			}
		}
		String params = JsonUtil.toJson(paramsMap);
		paramsWrapper.put("requestParams", params);

		if (RequestLogMode.REQ_PARAMS.containsOf(modes)) {
			logger.addLogText("== Request parameters ==> {} \n");
			logger.addArgs(params);
		}

		return paramsWrapper;

	}

	private String subResponseData(Object result, RelaxLogProperties.RequestLog properties) {
		Integer maxLen = properties.getMaxRespDataLength();
		if (result == null) {
			return StringPool.EMPTY;
		}
		String responseData = JsonUtil.toJson(result);
		if (maxLen == null || maxLen == 0) {
			return responseData;
		}
		if (responseData != null && responseData.length() > maxLen) {
			responseData = StringUtil.sub(responseData, 0, maxLen).concat("...");
		}
		return responseData;
	}

}
